package com.baomidou.springwind.controller.city;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.kisso.annotation.Permission;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.springwind.baidu.RouteMatrix;
import com.baomidou.springwind.controller.BaseController;
import com.baomidou.springwind.entity.NetworkNode;
import com.baomidou.springwind.enums.NodeTypeEnum;
import com.baomidou.springwind.pojo.*;
import com.baomidou.springwind.service.NetworkNodeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * 城市配送-运输成本相关
 *
 * @Author demo
 * @Date 2017-8-6
 */
@Controller
@RequestMapping("/city")
public class CityTransportController extends BaseController {

    @Autowired
    private NetworkNodeService networkNodeService;

    @Autowired
    private RouteMatrix routeMatrix;

    private ExecutorService pool = Executors.newFixedThreadPool(10);

    @RequestMapping("/transport")
    @Permission("1001")
    public String transport(Model model) {
        //象限分组
//        fetchGroup();
        return pjaxPath("/lpp/network/city/transport/main", model);
    }

    /**
     * 运输批次
     * @param type
     * @return
     */
    @ResponseBody
    @Permission("1001")
    @RequestMapping("/quadrant")
    public String getQuadrantData(int type) {
        EntityWrapper<NetworkNode> wrapper = new EntityWrapper<NetworkNode>();
        wrapper.eq("batch_id", getProjectId());
        wrapper.eq("node_type", NodeTypeEnum.CUSTOM.getType());
        wrapper.eq("operator_id", getCurrentUserId());
        wrapper.orderBy("id");
        List<NetworkNode> nodeList = networkNodeService.selectList(wrapper);
        CityTransportGroup group = new TransportGroupBuilder(nodeList).build();
        List<NetworkNode> nodes = null;
        if (type == 0) {
            nodes = group.getCenterGroup();
        } else if (type == 1) {
            nodes = group.getFirstGroup();
        } else if (type == 2) {
            nodes = group.getSecondGroup();
        } else if (type == 3) {
            nodes = group.getThirdGroup();
        } else if (type == 4) {
            nodes = group.getFourthGroup();
        } else if (type == 9) {
            nodes = group.getOtherGroup();
        } else if (type == 10) {
            nodes = nodeList;
        }
        Collections.sort(nodes, new Comparator<NetworkNode>() {
            @Override
            public int compare(NetworkNode o1, NetworkNode o2) {
                return o1.getAmount().compareTo(o2.getAmount());
            }
        });
        return callbackSuccess(nodes);
    }

    @ResponseBody
    @Permission("1001")
    @RequestMapping("/vechile")
    public String getVechileData(int type) {
        EntityWrapper<NetworkNode> wrapper = new EntityWrapper<NetworkNode>();
        wrapper.eq("batch_id", getProjectId());
        wrapper.eq("node_type", NodeTypeEnum.CUSTOM.getType());
        wrapper.orderBy("node_type");
        wrapper.eq("operator_id", getCurrentUserId());
        List<NetworkNode> nodeList = networkNodeService.selectList(wrapper);
        wrapper = new EntityWrapper<NetworkNode>();
        wrapper.eq("batch_id", getProjectId());
        wrapper.eq("node_type", NodeTypeEnum.DISTRIBUTE_CENTER.getType());
        NetworkNode dcNode = networkNodeService.selectOne(wrapper);

        CityTransportGroup group = new TransportGroupBuilder(nodeList).build();
        List<NetworkNode> nodes = null;
        if (type == 0) {
            nodes = group.getCenterGroup();
        } else if (type == 1) {
            nodes = group.getFirstGroup();
        } else if (type == 2) {
            nodes = group.getSecondGroup();
        } else if (type == 3) {
            nodes = group.getThirdGroup();
        } else if (type == 4) {
            nodes = group.getFourthGroup();
        } else {
            throw new RuntimeException();
        }
        Map<Long, TreeSet<RouteInfo>> routeMap = getRutoInfoMap(dcNode, nodes);
        Map<Long, NetworkNode> nodeMap = new HashMap<Long, NetworkNode>();
        for (NetworkNode node : nodes) {
            nodeMap.put(node.getId(), node);
        }
        Collections.sort(nodes, new Comparator<NetworkNode>() {
            @Override
            public int compare(NetworkNode o1, NetworkNode o2) {
                return o2.getAmount().compareTo(o1.getAmount());
            }
        });
        List<VehicleBatch> vehicleBatchList = simulate(dcNode, nodes, routeMap);
        JSONArray result = new JSONArray();
        for (VehicleBatch batch : vehicleBatchList) {
            JSONObject object = new JSONObject();
            object.put("vehicle", batch.getVehicle());
            Double totalAmount = 0d;
            for (Double amount : batch.getAmountList()) {
                totalAmount += amount;
            }
            object.put("amount", String.format("%.1f ", totalAmount));
            List<Point> pointList = new ArrayList<Point>(batch.getNodeList().size());

            for (Long nodeId : batch.getNodeList()) {
                NetworkNode node = nodeMap.get(nodeId);
                pointList.add(new Point(node.getCoordinateX(), node.getCoordinateY(), node.getNodeName()));
            }
            object.put("distribute", new Point(dcNode.getCoordinateX(), dcNode.getCoordinateY(), dcNode.getNodeName()));
            object.put("customs", pointList);
            Long startNodeId = dcNode.getId();
            Long distance = 0L;
            for (Long nodeId : batch.getNodeList()) {
                RouteInfo routeInfo = getRouteInfo(routeMap, startNodeId, nodeId);
                distance += routeInfo.distance;
                startNodeId = nodeId;
            }
            object.put("distance", String.format("%.1f", distance * 1.0f / 1000));
            startNodeId = dcNode.getId();
            Long duration = 0L;
            for (Long nodeId : batch.getNodeList()) {
                RouteInfo routeInfo = getRouteInfo(routeMap, startNodeId, nodeId);
                duration += routeInfo.duration;
                duration += (10 + 15) * 60;
                startNodeId = nodeId;
            }
            object.put("duration", duration);
            result.add(object);
        }

        return callbackSuccess(result);
    }

    @ResponseBody
    @Permission("1001")
    @RequestMapping("/vehicleCost")
    public String getVehicleCostData() {
        JSONArray array = new JSONArray();
        EntityWrapper<NetworkNode> wrapper = new EntityWrapper<NetworkNode>();
        wrapper.eq("batch_id", getProjectId());
        wrapper.eq("node_type", NodeTypeEnum.CUSTOM.getType());
        wrapper.orderBy("node_type");
        wrapper.eq("operator_id", getCurrentUserId());
        List<NetworkNode> nodeList = networkNodeService.selectList(wrapper);
        CityTransportGroup group = new TransportGroupBuilder(nodeList).build();
        wrapper = new EntityWrapper<NetworkNode>();
        wrapper.eq("batch_id", getProjectId());
        wrapper.eq("node_type", NodeTypeEnum.DISTRIBUTE_CENTER.getType());
        NetworkNode dcNode = networkNodeService.selectOne(wrapper);
        array.add(String.format("%.2f ", groupDistance(dcNode, group.getCenterGroup())));
        array.add(String.format("%.2f ", groupDistance(dcNode, group.getFirstGroup())));
        array.add(String.format("%.2f ", groupDistance(dcNode, group.getSecondGroup())));
        array.add(String.format("%.2f ", groupDistance(dcNode, group.getThirdGroup())));
        array.add(String.format("%.2f ", groupDistance(dcNode, group.getFourthGroup())));
        Double otherOil = 0d;
        for (NetworkNode other : group.getOtherGroup()) {
            otherOil += groupDistance(dcNode, Arrays.asList(other));
        }
        array.add(String.format("%.2f ", otherOil));
        request.getSession().setAttribute("vehicleCost" + getProjectId(), array);
        return callbackSuccess(array);
    }

    @ResponseBody
    @Permission("1001")
    @RequestMapping("/vehicleTime")
    public String getvehicleTimeData() {
        EntityWrapper<NetworkNode> wrapper = new EntityWrapper<NetworkNode>();
        wrapper.eq("batch_id", getProjectId());
        wrapper.eq("node_type", NodeTypeEnum.CUSTOM.getType());
        wrapper.orderBy("node_type");
        wrapper.eq("operator_id", getCurrentUserId());
        List<NetworkNode> nodeList = networkNodeService.selectList(wrapper);
        CityTransportGroup group = new TransportGroupBuilder(nodeList).build();
        wrapper = new EntityWrapper<NetworkNode>();
        wrapper.eq("batch_id", getProjectId());
        wrapper.eq("node_type", NodeTypeEnum.DISTRIBUTE_CENTER.getType());
        NetworkNode dcNode = networkNodeService.selectOne(wrapper);
        TreeMap<Integer, Integer> timeMap = new TreeMap<Integer, Integer>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1 - o2;
            }
        });
        addTimeVehicleMap(timeMap, groupDuration(dcNode, group.getCenterGroup()));
        addTimeVehicleMap(timeMap, groupDuration(dcNode, group.getFirstGroup()));
        addTimeVehicleMap(timeMap, groupDuration(dcNode, group.getSecondGroup()));
        addTimeVehicleMap(timeMap, groupDuration(dcNode, group.getThirdGroup()));
        addTimeVehicleMap(timeMap, groupDuration(dcNode, group.getFourthGroup()));
        for (NetworkNode other : group.getOtherGroup()) {
            addTimeVehicleMap(timeMap, groupDuration(dcNode, Arrays.asList(other)));
        }
        timeMap.put(timeMap.lastKey() + 1, 0);
        JSONObject result = new JSONObject();
        result.put("data", timeMap.values());
        result.put("name", timeMap.keySet());
        return callbackSuccess(result);
    }
    @ResponseBody
    @Permission("1001")
    @RequestMapping("/effect")
    public String workEffect() {
        EntityWrapper<NetworkNode> wrapper = new EntityWrapper<NetworkNode>();
        wrapper.eq("batch_id", getProjectId());
        wrapper.eq("node_type", NodeTypeEnum.CUSTOM.getType());
        wrapper.orderBy("node_type");
        wrapper.eq("operator_id", getCurrentUserId());
        List<NetworkNode> nodeList = networkNodeService.selectList(wrapper);
        wrapper = new EntityWrapper<NetworkNode>();
        wrapper.eq("batch_id", getProjectId());
        wrapper.eq("node_type", NodeTypeEnum.DISTRIBUTE_CENTER.getType());
        NetworkNode dcNode = networkNodeService.selectOne(wrapper);

        CityTransportGroup group = new TransportGroupBuilder(nodeList).build();
        List<NetworkNode> nodes = null;
        int type=4;
        JSONArray jsonArray=new JSONArray();
        for (int i=0;i<=type;i++){
            if (i == 0) {
                nodes = group.getCenterGroup();
            } else if (i == 1) {
                nodes = group.getFirstGroup();
            } else if (i == 2) {
                nodes = group.getSecondGroup();
            } else if (i == 3) {
                nodes = group.getThirdGroup();
            } else if (i == 4) {
                nodes = group.getFourthGroup();
            } else {
                throw new RuntimeException();
            }
            Map<Long, TreeSet<RouteInfo>> routeMap = getRutoInfoMap(dcNode, nodes);
            Map<Long, NetworkNode> nodeMap = new HashMap<Long, NetworkNode>();
            for (NetworkNode node : nodes) {
                nodeMap.put(node.getId(), node);
            }
            Collections.sort(nodes, new Comparator<NetworkNode>() {
                @Override
                public int compare(NetworkNode o1, NetworkNode o2) {
                    return o2.getAmount().compareTo(o1.getAmount());
                }
            });
            jsonArray=getTimes(dcNode,nodes,routeMap,nodeMap,jsonArray,i);
        }
        JSONObject result = new JSONObject();
        JSONArray jsonArray1=new JSONArray();
        JSONArray jsonArray2=new JSONArray();
        for (int i=0;i<jsonArray.size();i++){
            jsonArray1.add(jsonArray.getJSONObject(i).get("customs"));
            jsonArray2.add(jsonArray.getJSONObject(i).get("duration"));
        }
        result.put("s1", jsonArray1);
        result.put("s2", jsonArray2);
        return callbackSuccess(result);
    }
    public JSONArray getTimes(NetworkNode dcNode,List<NetworkNode> nodes,Map<Long, TreeSet<RouteInfo>> routeMap,Map<Long, NetworkNode> nodeMap,JSONArray jsonArray,int i){
        List<VehicleBatch> vehicleBatchList = simulate(dcNode, nodes, routeMap);
        int i2=0;
        for (VehicleBatch batch : vehicleBatchList) {
            JSONObject object = new JSONObject();
            object.put("vehicle", batch.getVehicle());
            StringBuffer stringBuffer=new StringBuffer();
            i2++;
            stringBuffer.append("第").append(i).append("象限").append("第").append(i2).append("批次");
            for (Long nodeId : batch.getNodeList()) {
                NetworkNode node = nodeMap.get(nodeId);
            //    stringBuffer=stringBuffer.append("-").append(node.getNodeName());
            }
            object.put("customs", stringBuffer);
            Long startNodeId = dcNode.getId();
            Long duration = 0L;
            for (Long nodeId : batch.getNodeList()) {
                RouteInfo routeInfo = getRouteInfo(routeMap, startNodeId, nodeId);
                duration += routeInfo.duration;
                duration += (10 + 15) * 60;
                startNodeId = nodeId;
            }
            object.put("duration", duration);
            jsonArray.add(object);
        }
        return jsonArray;
    }


    private void addTimeVehicleMap(Map<Integer, Integer> timeMap, Map<Integer, Integer> groupTime) {
        for (Integer time : groupTime.keySet()) {
            if (timeMap.containsKey(time)) {
                timeMap.put(time, timeMap.get(time) + groupTime.get(time));
            } else {
                timeMap.put(time, groupTime.get(time));
            }
        }
    }

    private Double groupDistance(NetworkNode dcNode, List<NetworkNode> nodeList) {
        Map<Long, TreeSet<RouteInfo>> routeMap = getRutoInfoMap(dcNode, nodeList);
        Collections.sort(nodeList, new Comparator<NetworkNode>() {
            @Override
            public int compare(NetworkNode o1, NetworkNode o2) {
                return o2.getAmount().compareTo(o1.getAmount());
            }
        });
        List<VehicleBatch> vehicleBatchList = simulate(dcNode, nodeList, routeMap);
        Double oil = 0D;
        for (VehicleBatch batch : vehicleBatchList) {
            Long startNodeId = dcNode.getId();
            Long distance = 0L;
            for (Long nodeId : batch.getNodeList()) {
                RouteInfo routeInfo = getRouteInfo(routeMap, startNodeId, nodeId);
                distance += routeInfo.distance;
                startNodeId = nodeId;
            }
            oil += distance / 1000d * 1.5 * batch.getVehicle();
        }
        return oil;
    }


    private Map<Long, TreeSet<RouteInfo>> getRutoInfoMap(final NetworkNode dcNode, final List<NetworkNode> nodeList) {
        final Map<Long, TreeSet<RouteInfo>> routeMap = new LinkedHashMap<Long, TreeSet<RouteInfo>>();
        final CountDownLatch latch = new CountDownLatch(nodeList.size() + 1);
        pool.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    TreeSet<RouteInfo> route = routeMatrix.getBaiduApiResult(dcNode, nodeList);
                    routeMap.put(dcNode.getId(), route);
                } finally {
                    latch.countDown();
                }
            }
        });
        for (final NetworkNode origins : nodeList) {
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        routeMap.put(origins.getId(), routeMatrix.getBaiduApiResult(origins, nodeList));
                    } finally {
                        latch.countDown();
                    }
                }
            });
        }
        try {
            latch.await(60, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RuntimeException("加载需求点距离异常");
        }
        return routeMap;
    }

    private Map<Integer, Integer> groupDuration(NetworkNode dcNode, List<NetworkNode> nodeList) {
        Map<Long, TreeSet<RouteInfo>> routeMap = getRutoInfoMap(dcNode, nodeList);
        List<VehicleBatch> vehicleBatchList = simulate(dcNode, nodeList, routeMap);
        Map<Integer, Integer> vehicleTime = new LinkedHashMap<Integer, Integer>();
        int line = 0;
        for (VehicleBatch batch : vehicleBatchList) {
            Long startNodeId = dcNode.getId();
            Long duration = 0L;
            for (Long nodeId : batch.getNodeList()) {
                RouteInfo routeInfo = getRouteInfo(routeMap, startNodeId, nodeId);
                duration += routeInfo.duration;
                duration += (10 + 15) * 60;
                startNodeId = nodeId;
            }
            int hours = Math.round(duration.floatValue() / 3600);
            for (int i = 1; i <= hours; i++) {
                vehicleTime.put(line + i, batch.getVehicle());
            }
            line = line + hours;
        }
        return vehicleTime;
    }

    private List<VehicleBatch> simulate(NetworkNode dcNode, List<NetworkNode> nodes, Map<Long, TreeSet<RouteInfo>> routeMap) {
        int batch = 2;
        int fullLoad = 15;
        int shelf = 550;
        int totalShelf = 0;
        for (NetworkNode node : nodes) {
            node.setShelf(new BigDecimal(node.getAmount().doubleValue() * 1.3 / 30 / shelf).setScale(1, RoundingMode.UP).doubleValue());
            totalShelf += node.getShelf();
        }
        int vehicle = (int) Math.ceil(totalShelf * 1.0f / fullLoad / batch);
        Map<Long, NetworkNode> finderMap = new HashMap<Long, NetworkNode>();
        for (NetworkNode node : nodes) {
            finderMap.put(node.getId(), node);
        }
        int vehicleAll = vehicle * fullLoad;
        boolean exclude = true;
        List<VehicleBatch> vehicleBatchList = new ArrayList<VehicleBatch>(batch);
        while (batch-- > 0) {
            VehicleBatch vBatch = new VehicleBatch();
            double amount = 0d;
            NetworkNode node = exclude ? nodes.get(0) : find(finderMap, routeMap, dcNode.getId());
            do {
                if (amount + node.getShelf() >= vehicleAll) {
                    node.setShelf(node.getShelf() - (vehicleAll - amount));
                    vBatch.addAmount(vehicleAll - amount);
                    if (node.getShelf() == 0) {
                        finderMap.remove(node.getId());
                    }
                    amount = vehicleAll;
                    vBatch.addNode(node.getId());
                    break;
                } else {
                    amount += node.getShelf();
                    vBatch.addAmount(node.getShelf());
                    finderMap.remove(node.getId());
                    exclude = false;
                }
                vBatch.addNode(node.getId());
                node = find(finderMap, routeMap, node.getId());
            } while (node != null);
            vBatch.setVehicle((int) Math.ceil(amount / fullLoad));
            vehicleBatchList.add(vBatch);
        }
        return vehicleBatchList;
    }


    private NetworkNode find(Map<Long, NetworkNode> finderMap, Map<Long, TreeSet<RouteInfo>> routeMap, Long nodeId) {
        NetworkNode node = null;
        TreeSet<RouteInfo> routeInfos = routeMap.get(nodeId);
        for (RouteInfo info : routeInfos) {
            node = finderMap.get(info.id);
            if (node != null) {
                break;
            }
        }
        return node;
    }

    private RouteInfo getRouteInfo(Map<Long, TreeSet<RouteInfo>> routeMap, Long start, Long end) {
        TreeSet<RouteInfo> routeInfos = routeMap.get(start);
        for (RouteInfo info : routeInfos) {
            if (end.equals(info.id)) {
                return info;
            }
        }
        return null;
    }
}
